package ci.web;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.DefaultEventExecutor;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import ci.web.codec.CiHttpHandler;
import ci.web.codec.CiHttpWebSocketHandler;
import ci.web.codec.WebChannelIniter;
import ci.web.core.CiContext;
import ci.web.core.CiWebService;
import ci.web.router.CiRouter;
import ci.web.router.CiWebSocketHandler;

/**
 * ci-服务中心
 * @author zhh
 */
public class CiService implements CiWebService {

    private static InternalLogger logger = InternalLoggerFactory.getInstance(CiService.class);
    
    protected NioEventLoopGroup bossGroup;
    protected NioEventLoopGroup workerGroup;
    protected ServerBootstrap boot;
    protected ChannelFuture bossChannel;
    
    protected CiHttpHandler handler;
    protected final CiWebSocketHandlerProxy wsHandlerProxy;
    
    protected CiRouter router;
    protected CiConfig config;
    
    protected final SslContext ssl;
    
    public CiService(String configFile){
        this(CiConfig.load(configFile));
    }
    public CiService(CiConfig config){
        this(config, null);
    }
    public CiService(CiConfig config, SslContext sslContext) {
        if(config==null){
            throw new RuntimeException("CiConfig can't be null!");
        }
        router = new CiRouter();
        this.config = config;
        this.wsHandlerProxy = new CiWebSocketHandlerProxy();
        this.ssl = sslContext;
        
        DiskFileUpload.deleteOnExitTemporaryFile = false;
        DiskAttribute.deleteOnExitTemporaryFile = false;
    }
    
    public void start(){
        if(boot!=null){
            stop();
        }
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup(config.workThread());
        boot = new ServerBootstrap();
        
        boot.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_REUSEADDR, CiSystemConfig.SocketReuseAddr)
        .childOption(ChannelOption.SO_KEEPALIVE, CiSystemConfig.SocketKeepAlive)
        .option(ChannelOption.TCP_NODELAY, CiSystemConfig.TCP_NODELAY)
        .option(ChannelOption.SO_BACKLOG, CiSystemConfig.SocketBackLog)
        .option(ChannelOption.SO_RCVBUF, CiSystemConfig.SocketRcvbufSize)
        .option(ChannelOption.SO_SNDBUF, CiSystemConfig.SocketSndbufSize)
        .childHandler(channelIniter());
        
        bossChannel = boot.bind(config.port());
        try {
            bossChannel.sync();
            logger.info("Web server started at port:" + config.port() + '.');
            logger.info("Open your browser and navigate to http"+(ssl==null?"":"s")+"://localhost:" + config.port() + '/');
        } catch (InterruptedException e) {
        }
        reload();
    }
    
    public void stop(){
        if(boot!=null){
            handler.disable();
            long waitTm = Math.min(10000L, httpConnectNum()*2);
            try {
                Thread.sleep(waitTm);
            } catch (InterruptedException e) {
            }
            boot = null;
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            bossChannel.channel().close();
            dev(false);
        }
    }
    
    public boolean isAlive(){
        return boot!=null;
    }

    public void setWebSocketHandler(CiWebSocketHandler handler){
        wsHandlerProxy.setHandler(handler);
    }
    public CiWebSocketHandler getWebSocketHandler(){
        return wsHandlerProxy.getHandler();
    }
    public ChannelGroup webSocketGroup(){
        return wsHandlerProxy.channelGroup();
    }
    
    private CiReload reloader;
    public void dev(){
    	dev(true);
    }
    public synchronized void dev(boolean open){
    	if(reloader!=null){
            reloader.stop();
            reloader = null;
        }
    	if(open){
    		reloader = new CiReload(this, router.urls());
    		reloader.start();
    	}
    }
    
    public void reload() {
		logger.info("start reLoad handlers... ...");
        CiRouter r = new CiRouter();
        r.setHandler(config.handlerDir(), config.handlerPackage());
        r.setFile(config.fileDir(), config.filePath(), config.fileMaxAge(), config.exceptExtend());
        if(router!=null){
            r.getHandler().add(router.getHandler().handlers());
        }
        this.router = r;
		logger.info("end reLoad handlers");
    }

    public int httpConnectNum(){
        CiHttpHandler h = handler;
        return h==null?0:h.connectNum();
    }
    
    private ChannelInitializer<?> channelIniter() {
        if(config.webSocket()){
            handler = new CiHttpWebSocketHandler(wsHandlerProxy, config.webSocketPath(), config.limit()) {
                @Override
                protected boolean doProcess(CiContext context) throws Exception {
                    return router.call(context);
                }
            };
        }else{
            handler = new CiHttpHandler(config.limit()) {
                @Override
                protected boolean doProcess(CiContext context) throws Exception {
                    return router.call(context);
                }
            };
        }
        WebChannelIniter initer = new WebChannelIniter(config.maxMsgSize(), config.host(), config.corsOrgins(), handler);
        initer.ssl = this.ssl;
        return initer;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    private class CiWebSocketHandlerProxy implements CiWebSocketHandler {

        private ChannelGroup group;
        private CiWebSocketHandler handler;
        private CiWebService service;
        public CiWebSocketHandlerProxy(){
            group = new DefaultChannelGroup(new DefaultEventExecutor());
        }
        public CiWebSocketHandler getHandler(){
            return handler;
        }
        public void setHandler(CiWebSocketHandler h){
            this.handler = h;
            if(h!=null){
                this.handler.init(service);
            }
        }

        public ChannelGroup channelGroup(){
            return group;
        }
        
        public void init(CiWebService service){
            this.service = service;
        }
        
        @Override
        public void onOpen(Channel channel) {
            group.add(channel);
            if(handler!=null){
                handler.onOpen(channel);
            }
        }

        @Override
        public void onText(Channel channel, String data) {
            if(handler!=null){
                handler.onText(channel, data);
            }
        }

        @Override
        public void onBytes(Channel channel, byte[] bytes) {
            if(handler!=null){
                handler.onBytes(channel, bytes);
            }
        }

        @Override
        public void onClose(Channel channel, boolean lost) {
            if(handler!=null){
                handler.onClose(channel, lost);
            }
        }

    }
}
